/**
 * 运行模块
 * 负责运行编译模块生成的可执行文件
 */

#pragma once
#include<iostream>
#include<string>
#include"../Comm/Detail.hpp"
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/time.h>
#include<sys/resource.h>
#include<fcntl.h>


namespace OnlineOJ
{
    namespace ns_Runner
    {
        //运行程序
        class Runner
        {
        public:

            //设置程序运行时占用资源的限制
            static void SetProcessLimit(const int& cpu_limit,const int& mem_limit)
            {
                //设置使用cpu资源上限
                struct rlimit cpu_rl;
                cpu_rl.rlim_cur=cpu_limit;
                cpu_rl.rlim_max=RLIM_INFINITY;
                setrlimit(RLIMIT_CPU,&cpu_rl);

                //设置内存资源上限
                struct rlimit mem_rl;
                mem_rl.rlim_cur=mem_limit*1024;
                mem_rl.rlim_max=RLIM_INFINITY;
                setrlimit(RLIMIT_AS,&mem_rl);
            }
        

            //传入文件名(不带路径、后缀)，执行对应的可执行程序
            //返回值=0，程序正常运行，运行结果保存在对应文件中
            //返回值>0，运行异常
            //返回值<0，内部错误
            //cpu_limit:程序运行时允许使用的最大CPU资源
            //mem_limit:程序运行时允许使用的最大内存资源(KB)
            static int Run(const std::string& filename,const int& cpu_limit,const int& mem_limit)
            {
                /**
                 * 创建子进程去运行程序
                 * 运行程序只关心要运行的程序是谁
                 * 以及是否正常运行
                 * 不关心运行结果是否正确
                 */
                //可执行程序
                std::string _execute=ns_Detail::PathUtil::Exe(filename);
                //需要用到的文件
                std::string _stdin=ns_Detail::PathUtil::StdIn(filename);
                std::string _stdout=ns_Detail::PathUtil::StdOut(filename);
                std::string _stderr=ns_Detail::PathUtil::StdError(filename);

                //打开对应文件
                umask(0);//权限掩码置0
                int _stdin_fd=open(_stdin.c_str(),O_CREAT|O_RDONLY,0644);
                int _stdout_fd=open(_stdout.c_str(),O_CREAT|O_WRONLY,0644);
                int _stderr_fd=open(_stderr.c_str(),O_CREAT|O_WRONLY,0644);
                if(_stdin_fd<0||_stdout_fd<0||_stderr_fd<0)
                {
                    LOG(ERROR,"运行时，标准文件打开失败！\n");
                    return -1;
                }

                pid_t pid=fork();
                if(pid<0)
                {
                    close(_stdin_fd);
                    close(_stdout_fd);
                    close(_stderr_fd);
                    LOG(ERROR,"运行时，子进程创建失败！\n");
                    return -2;
                }
                else if(pid==0)//子进程
                {
                    //重定向子进程的标准输入、标准输出、标准错误
                    dup2(_stdin_fd,0);
                    dup2(_stdout_fd,1);
                    dup2(_stderr_fd,2);
                    //设置程序占用资源上限
                    SetProcessLimit(cpu_limit,mem_limit);
                    //程序替换执行程序
                    execl(_execute.c_str()/*要执行的程序*/,_execute.c_str()/*如何执行*/,nullptr);

                    exit(1);
                }
                else//父进程
                {
                    close(_stdin_fd);
                    close(_stdout_fd);
                    close(_stderr_fd);
                    int status=0;//保存子进程运行结果
                    waitpid(pid,&status,0);
                    LOG(INFO,"程序运行完毕，info：%d\n",status&0x7f);
                    return status&0x7f;
                }
                return 0;
            }
        };
    }
}